using System;
using System.ComponentModel;
using System.Xml.Serialization;

namespace SharpGL.SceneGraph.Evaluators
{
	/// <summary>
	/// The NURBS class is the base for NURBS objects, such as curves and surfaces.
	/// </summary>
	[Serializable()]
	public abstract class NurbsBase : Evaluator, IHasOpenGLContext
	{
        #region Private Fields

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        private OpenGL currentOpenGLContext = null;

        #endregion Private Fields

        public enum NurbsDisplayMode : uint
        {
            OutlinePatch = OpenGL.GLU_OUTLINE_PATCH,
            OutlinePolygon = OpenGL.GLU_OUTLINE_POLYGON,
            Fill = OpenGL.GLU_FILL
        }

        /// <summary>
        /// This is the pointer to the underlying NURBS object.
        /// </summary>
        protected IntPtr nurbsRenderer = IntPtr.Zero;

        /// <summary>
        /// The display mode.
        /// </summary>
        protected NurbsDisplayMode displayMode = NurbsDisplayMode.OutlinePolygon;

        /// <summary>
        /// Create in the context of the supplied OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        protected virtual void OnCreateInContext(OpenGL gl)
        {
            //  Create the NURBS renderer.
            nurbsRenderer = gl.NewNurbsRenderer();
        }

        /// <summary>
        /// Destroy in the context of the supplied OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        protected virtual void OnDestroyInContext(OpenGL gl)
        {
            //  Destroy the NURBS renderer.
            gl.DeleteNurbsRenderer(nurbsRenderer);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NurbsBase"/> class.
        /// </summary>
        public NurbsBase()
        {
            Name = "NURBS Object";
        }

        /// <summary>
        /// Create in the context of the supplied OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public virtual void CreateInContext(OpenGL gl)
        {
            if (!OpenGL.Equals(gl, currentOpenGLContext))
            {
                if (OpenGL.IsValidContext(currentOpenGLContext))
                    throw new Exception("Cannot create '" +
                                        Name +
                                        "' in context '" +
                                        (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                        "' - it is already created in '" +
                                        (OpenGL.IsValidContext(currentOpenGLContext) ? currentOpenGLContext.Renderer : "Null") +
                                        "' OpenGL context.");


                if (OpenGL.IsValidContext(gl))
                {
                    currentOpenGLContext = gl;

                    // Call on create in OpenGL context function.
                    OnCreateInContext(gl);

                    NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.CreateInContext));
                }
            }
        }

        /// <summary>
        /// Destroy in the current OpenGL instance.
        /// </summary>
        public virtual void DestroyInContext()
        {
            if (OpenGL.IsValidContext(currentOpenGLContext))
            {
                NotifyContextChanged(this, new ContextChangeEventArgs(ContextChangeAction.DestroyInContext));

                // Call on destroy in OpenGL context function.
                OnDestroyInContext(currentOpenGLContext);

                currentOpenGLContext = null;
            }
        }

        /// <summary>
        /// Render to the provided instance of OpenGL.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="renderMode">The render mode.</param>
        public override void Render(OpenGL gl, RenderMode renderMode)
        {
            if (!OpenGL.Equals(CurrentOpenGLContext, gl))
                throw new Exception("Can't render '" +
                                    Name +
                                    "' in context '" +
                                    (OpenGL.IsValidContext(gl) ? gl.Renderer : "Null") +
                                    "' - it is created in '" +
                                    (OpenGL.IsValidContext(CurrentOpenGLContext) ? CurrentOpenGLContext.Renderer : "Null") +
                                    "' OpenGL context.");

            if (OpenGL.IsValidContext(gl))
            {
                //	Set the properties.
                gl.NurbsProperty(nurbsRenderer, (int)OpenGL.GLU_DISPLAY_MODE, (float)displayMode);
            }
        }

        /// <summary>
        /// Current OpenGL context
        /// </summary>
        [Browsable(false)]
        [XmlIgnore]
        public OpenGL CurrentOpenGLContext
        {
            get { return currentOpenGLContext; }
            protected set { currentOpenGLContext = value; }
        }
        
        /// <summary>
        /// Gets or sets the display mode.
        /// </summary>
        /// <value>
        /// The display mode.
        /// </value>
		[Description("The way that the NURBS object is rendered."), Category("NURBS")]
		public NurbsDisplayMode DisplayMode
		{
			get {return displayMode;}
			set {displayMode = value;}
		}
    }
}
